package evl

import (
	"testing"

	"gitcode.com/deyiyangyang/bampoo/lex"
	"gitcode.com/deyiyangyang/bampoo/obj"
	"gitcode.com/deyiyangyang/bampoo/par"
)

func TestLetEdgeCases(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected interface{}
	}{
		{
			name:     "let with zero bindings",
			input:    "(let () 42)",
			expected: int64(42),
		},
		{
			name:     "let with empty body",
			input:    "(let ((x 5)))",
			expected: nil, // Should return nil
		},
		{
			name:     "let with multiple expressions in body",
			input:    "(let ((x 5)) (display x) (+ x 10) (* x 2))",
			expected: int64(10),
		},
		{
			name:     "nested let with same variable names",
			input:    "(let ((x 5)) (let ((x 10)) x))",
			expected: int64(10),
		},
		{
			name:     "let binding with function call",
			input:    "(let ((x (+ 1 2 3))) x)",
			expected: int64(6),
		},
		{
			name:     "let with string binding",
			input:    `(let ((name "hello")) name)`,
			expected: "hello",
		},
		{
			name:     "let with lambda binding",
			input:    "(let ((f (lambda (x) (* x 2)))) (f 5))",
			expected: int64(10),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create lexer and parser
			lexer := lex.New(tt.input)
			parser := par.New(lexer)

			// Parse the program
			program := parser.ParseProgram()
			if len(parser.Errors()) > 0 {
				t.Fatalf("parser errors: %v", parser.Errors())
			}

			// Create evaluator
			env := obj.NewEnvironment()
			evaluator := New(env)

			// Evaluate the program
			result, err := evaluator.Eval(program)
			if err != nil {
				t.Fatalf("evaluation error: %v", err)
			}

			// Check result based on expected type
			switch expected := tt.expected.(type) {
			case int64:
				intResult, ok := result.(*obj.IntegerValue)
				if !ok {
					t.Fatalf("expected IntegerValue, got %T", result)
				}
				if intResult.Value != expected {
					t.Errorf("expected %d, got %d", expected, intResult.Value)
				}
			case string:
				strResult, ok := result.(*obj.StringValue)
				if !ok {
					t.Fatalf("expected StringValue, got %T", result)
				}
				if strResult.Value != expected {
					t.Errorf("expected %q, got %q", expected, strResult.Value)
				}
			case nil:
				if _, ok := result.(*obj.NilValue); !ok {
					t.Fatalf("expected NilValue, got %T", result)
				}
			}
		})
	}
}

func TestLetParserErrors(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		wantErr  bool
		errType  string
	}{
		{
			name:    "let without opening paren",
			input:   "(let (x 5) x)",
			wantErr: true,
			errType: "expected '(' for binding",
		},
		{
			name:    "let with invalid binding format",
			input:   "(let ((x)) x)",
			wantErr: true,
			errType: "expected value in binding",
		},
		{
			name:    "let without variable name",
			input:   "(let ((5)) 5)",
			wantErr: true,
			errType: "expected symbol in binding",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create lexer and parser
			lexer := lex.New(tt.input)
			parser := par.New(lexer)

			// Parse the program
			program := parser.ParseProgram()
			
			if tt.wantErr {
				if len(parser.Errors()) == 0 {
					t.Fatalf("expected parser error but got none")
				}
				// Check if error contains expected type
				found := false
				for _, err := range parser.Errors() {
					if contains(err, tt.errType) {
						found = true
						break
					}
				}
				if !found {
					t.Errorf("expected error containing %q, got %v", tt.errType, parser.Errors())
				}
			} else {
				if len(parser.Errors()) > 0 {
					t.Fatalf("unexpected parser errors: %v", parser.Errors())
				}
			}

			// If parsing succeeded, try evaluation
			if len(parser.Errors()) == 0 {
				env := obj.NewEnvironment()
				evaluator := New(env)
				_, err := evaluator.Eval(program)
				if err != nil && !tt.wantErr {
					t.Fatalf("unexpected evaluation error: %v", err)
				}
			}
		})
	}
}